package org.example.judge.config;

import com.github.dockerjava.api.DockerClient;



import cn.hutool.core.collection.CollectionUtil;
import cn.hutool.core.io.FileUtil;
import com.github.dockerjava.api.command.CreateContainerCmd;
import com.github.dockerjava.api.command.CreateContainerResponse;
import com.github.dockerjava.api.command.ListImagesCmd;
import com.github.dockerjava.api.command.PullImageCmd;
import com.github.dockerjava.api.command.PullImageResultCallback;
import com.github.dockerjava.api.model.Bind;
import com.github.dockerjava.api.model.Container;
import com.github.dockerjava.api.model.HostConfig;
import com.github.dockerjava.api.model.Image;
import com.github.dockerjava.api.model.Volume;
import lombok.extern.slf4j.Slf4j;
import org.example.common.core.constants.JudgeConstants;

import java.io.File;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.concurrent.ArrayBlockingQueue;
import java.util.concurrent.BlockingQueue;

@Slf4j
public class DockerSandBoxPool {

    //docker 客户端对象
    private DockerClient dockerClient;

    //需要拉取的docker镜像
    private String sandboxImage;

    //容器内的挂载点
    private String volumeDir;

    private Long memoryLimit;

    //内存+swap的最大限制
    private Long memorySwapLimit;

    private Long cpuLimit;

    //最大核心数
    private int poolSize;

    private String containerNamePrefix;

    //阻塞队列容器池
    private BlockingQueue<String> containerQueue;

    //存储docker的name和id
    private Map<String, String> containerNameMap;

    //给成员变量进行复制操作的构造方法
    public DockerSandBoxPool(DockerClient dockerClient,
                             String sandboxImage,
                             String volumeDir, Long memoryLimit,
                             Long memorySwapLimit, Long cpuLimit,
                             int poolSize, String containerNamePrefix) {

        this.dockerClient = dockerClient;
        this.sandboxImage = sandboxImage;
        this.volumeDir = volumeDir;
        this.memoryLimit = memoryLimit;
        this.memorySwapLimit = memorySwapLimit;
        this.cpuLimit = cpuLimit;
        this.poolSize = poolSize;
        this.containerQueue = new ArrayBlockingQueue<>(poolSize);
        this.containerNamePrefix = containerNamePrefix;
        this.containerNameMap = new HashMap<>();
    }

    //初始化容器池的
    public void initDockerPool() {
        log.info("------  创建容器开始  -----");
        for(int i = 0; i < poolSize; i++) {
            //通过容器前缀 + — + i生成容器的containerName
            createContainer(containerNamePrefix + "-" + i);
        }
        log.info("------  创建容器结束  -----");
    }

    //获取阻塞队列中的docker容器
    public String getContainer() {
        try {
            return containerQueue.take();
        } catch (InterruptedException e) {
            throw new RuntimeException(e);
        }
    }

    //归还docker容器给阻塞队列
    public void returnContainer(String containerId) {
        containerQueue.add(containerId);
    }

    //创建容器
    private void createContainer(String containerName) {
        //获取Java API客户端的所有docker容器列表，withShowAll(true): 包括已经停止的容器
        List<Container> containerList = dockerClient.listContainersCmd().withShowAll(true).exec();

        if (!CollectionUtil.isEmpty(containerList)) {
            //容器名
            String names = JudgeConstants.JAVA_CONTAINER_PREFIX + containerName;
            for (Container container : containerList) {
                //Docker 容器可以有多个名称（主名称 + 网络别名）  返回的是字符串数组，包含容器所有关联的名称
                String[] containerNames = container.getNames();
                //判断所需容器是否为containerName
                if (containerNames != null && containerNames.length > 0 && names.equals(containerNames[0])) {
                    //created: 容器已创建但从未启动  exited: 容器已启动但已退出
                    if ("created".equals(container.getState()) || "exited".equals(container.getState())) {
                        //启动容器指令的方法
                        dockerClient.startContainerCmd(container.getId()).exec();
                    }
                    containerQueue.add(container.getId());
                    //映射对应的容器Id和容器name
                    containerNameMap.put(container.getId(), containerName);
                    return;
                }
            }
        }

        //当找不到对应的容器时，会执行下方代码创建容器

        //拉取镜像
        pullJavaEnvImage();
        //创建容器  限制资源   控制权限

        // 1.获取主机配置
        HostConfig hostConfig = getHostConfig(containerName);

        //2.容器创建
        CreateContainerCmd containerCmd = dockerClient
                .createContainerCmd(JudgeConstants.JAVA_ENV_IMAGE)
                .withName(containerName);

        //3.容器权限设置
        CreateContainerResponse createContainerResponse = containerCmd
                .withHostConfig(hostConfig)
                .withAttachStderr(true)
                .withAttachStdout(true)
                .withTty(true)
                .exec();

        //记录容器id
        String containerId = createContainerResponse.getId();
        //启动容器
        dockerClient.startContainerCmd(containerId).exec();
        //将容器的Id传递给阻塞队列，这代表这个容器加入到阻塞队列中
        containerQueue.add(containerId);
        //容器Id和name的map映射保存
        containerNameMap.put(containerId, containerName);
    }

    //拉取镜像
    private void pullJavaEnvImage() {
        //在docker客户端执行获取本地镜像列表
        ListImagesCmd listImagesCmd = dockerClient.listImagesCmd();
        //接收镜像列表
        List<Image> imageList = listImagesCmd.exec();


        //判断目标价镜像是否存在
        for (Image image : imageList) {
            //getRepoTags(): 获取镜像的所有仓库标签（如["java:8", "java:latest"]）
            String[] repoTags = image.getRepoTags();
            if (repoTags != null && repoTags.length > 0 && sandboxImage.equals(repoTags[0])) {
                // 镜像已存在，直接返回
                return;
            }
        }


        //镜像不存在， 使用pullImageCmd(sandboxImage): 创建拉取指定镜像的命令
        PullImageCmd pullImageCmd = dockerClient.pullImageCmd(sandboxImage);
        try {
            //执行拉取命令，传入回调处理器
            pullImageCmd.exec(new PullImageResultCallback()).awaitCompletion();
        } catch (InterruptedException e) {
            throw new RuntimeException(e);
        }
    }

    //限制资源   控制权限  创建并配置 Docker 容器的主机配置对象
    private HostConfig getHostConfig(String containerName) {
        HostConfig hostConfig = new HostConfig();

        //设置挂载目录，指定用户代码路径
        String userCodeDir = createContainerDir(containerName);

        //volumeDir: 容器内的挂载点（如/app/code）  当执行javac Main.java时，容器内实际操作的是挂载目录中的文件：
        hostConfig.setBinds(new Bind(userCodeDir, new Volume(volumeDir)));
        // volumeDir/Main.java  ← 容器内路径
        //   ↓
        // /pool/container123/Main.java  ← 主机实际路径

        //限制docker容器使用资源
        hostConfig.withMemory(memoryLimit);
        hostConfig.withMemorySwap(memorySwapLimit);
        hostConfig.withCpuCount(cpuLimit);

        //禁用网络
        hostConfig.withNetworkMode("none");

        //禁止在root目录写文件
        hostConfig.withReadonlyRootfs(true);

        return hostConfig;
    }

    //获取与特定Docker容器关联的用户代码存储目录路径
    public String getCodeDir(String containerId) {
        String containerName = containerNameMap.get(containerId);
        log.info("containerName：{}", containerName);
        return System.getProperty("user.dir") + File.separator + JudgeConstants.CODE_DIR_POOL + File.separator + containerName;
    }


    //为每个容器，创建的指定挂载文件(分隔不同的用户代码文件)
    private String createContainerDir(String containerName) {
        //一级目录  存放所有容器的挂载目录
        String codeDir = System.getProperty("user.dir") + File.separator + JudgeConstants.CODE_DIR_POOL;
        if (!FileUtil.exist(codeDir)) {
            FileUtil.mkdir(codeDir);
        }
        return codeDir + File.separator + containerName;
    }

}

